using System;
using System.Globalization;
using Microsoft.Xna.Framework;
using Phoenix.Xna.Framework.Properties;

namespace Phoenix.Xna.Framework.Graphics.Animation
{
    [Serializable]
    public struct Pose : IEquatable<Pose>
    {
        private static readonly Pose _identity;

        public static Pose Identity { get { return _identity; } }
        public Vector3 Translation;
        public Quaternion Orientation;
        public Vector3 Scale;

        static Pose()
        {
            _identity.Orientation = Quaternion.Identity;
            _identity.Translation = Vector3.Zero;
            _identity.Scale = Vector3.Zero;
        }

        public static Pose Interpolate(Pose pose1, Pose pose2, float amount, InterpolationMode translationInterpolation, 
            InterpolationMode orientationInterpolation, InterpolationMode scaleInterpolation)
        {
            Pose resultPose;

            if (amount < 0 || amount > 1)
            {
                throw new ArgumentOutOfRangeException(string.Format(Resources.ArgumentOutOfRangeException, 0.0, 1.0));
            }

            Quaternion orientation1 = pose1.Orientation;
            Quaternion orientation2 = pose2.Orientation;
            Quaternion resultOrientation = Quaternion.Identity;
            Vector3 translation1 = pose1.Translation;
            Vector3 translation2 = pose2.Translation;
            Vector3 resultTranslation = Vector3.Zero;
            Vector3 scale1 = pose1.Scale;
            Vector3 scale2 = pose2.Scale;
            Vector3 resultScale = Vector3.Zero;

            switch (translationInterpolation)
            {
                case InterpolationMode.None:
                    resultPose.Translation = pose1.Translation;
                    break;
                case InterpolationMode.Linear:
                    Vector3.Lerp(ref translation1, ref translation2, amount, out resultTranslation);
                    break;
                case InterpolationMode.Cubic:
                    Vector3.SmoothStep(ref translation1, ref translation2, amount, out resultTranslation);
                    break;
                default:
                    throw new InvalidOperationException(string.Format(Resources.NotSupportedException, "Translation Interpolation"));
            }

            switch (orientationInterpolation)
            {
                case InterpolationMode.None:
                    resultPose.Orientation = pose1.Orientation;
                    break;
                case InterpolationMode.Linear:
                    Quaternion.Lerp(ref orientation1, ref orientation2, amount, out resultOrientation);
                    break;
                case InterpolationMode.Spherical:
                    Quaternion.Slerp(ref orientation1, ref orientation2, amount, out resultOrientation);
                    break;
                default:
                    throw new InvalidOperationException(string.Format(Resources.NotSupportedException, "Orientation Interpolation"));
            }

            switch (scaleInterpolation)
            {
                case InterpolationMode.None:
                    resultPose.Scale = pose1.Scale;
                    break;
                case InterpolationMode.Linear:
                    Vector3.Lerp(ref scale1, ref scale2, amount, out resultScale);
                    break;
                case InterpolationMode.Cubic:
                    Vector3.SmoothStep(ref scale1, ref scale2, amount, out resultScale);
                    break;
                default:
                    throw new InvalidOperationException(string.Format(Resources.NotSupportedException, "Scale Interpolation"));
            }

            SetPoseData(out pose1, orientation1, translation1, scale1);
            SetPoseData(out pose2, orientation2, translation2, scale2);
            SetPoseData(out resultPose, resultOrientation, resultTranslation, resultScale);
            return resultPose;
        }

        public override bool Equals(object obj)
        {
            if (System.Object.ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj != null && obj is Pose)
            {
                Pose pose = (Pose)obj;
                return (Translation == pose.Translation && Orientation == pose.Orientation && Scale == pose.Scale);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return (Translation.GetHashCode() + Orientation.GetHashCode() + Scale.GetHashCode());
        }

        public static bool operator ==(Pose left, Pose right)
        {
            if (System.Object.ReferenceEquals(left, right))
            {
                return true;
            }

            if (!System.Object.ReferenceEquals(left, null))
            {
                if (System.Object.ReferenceEquals(right, null))
                {
                    return false;
                }
                else
                {
                    return left.Equals(right);
                }
            }

            return false;
        }

        public static bool operator !=(Pose left, Pose right)
        {
            return (left == right) ? false : true;
        }

        public override string ToString()
        {
            CultureInfo currentCulture = CultureInfo.CurrentCulture;
            return string.Format(currentCulture, "{{Translation:{0}\n Orientation:{1}\n Scale:{2}\n}}", 
                new object[] 
                { 
                    Translation.ToString(), 
                    Orientation.ToString(),
                    Scale.ToString() 
                });
        }

        private static void SetPoseData(out Pose pose, Quaternion orientation, Vector3 translation, Vector3 scale)
        {
            pose.Orientation = orientation;
            pose.Translation = translation;
            pose.Scale = scale;
        }

        #region IEquatable<Pose> Members

        public bool Equals(Pose other)
        {
            return (Translation == other.Translation && Orientation == other.Orientation && Scale == other.Scale);
        }

        #endregion
    }
}